package eu.jk.config;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.util.LinkedList;
import java.util.List;

import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import eu.jk.config.PathResolver.Token;

/**
 * 
 * @author WZZ
 * 
 */
final class XMLEngine extends ConfigEngine {

	Document xml = null;

	String currentPath = null;

	@Override
	public void init(Reader reader) throws ConfigException {
		SAXReader sr = new SAXReader();
		try {
			xml = sr.read(reader);
		} catch (DocumentException e) {
			throw new ConfigException(e);
		}
	}

	@Override
	public void init(String path, String encoding) throws ConfigException {
		try {
			init(new InputStreamReader(new FileInputStream(new File(path)),
					encoding));
		} catch (FileNotFoundException e) {
			throw new ConfigException(e);
		} catch (UnsupportedEncodingException e) {
			throw new ConfigException(e);
		}
	}

	@Override
	public String getValue(String path) {
		List<String> values = getValues(path);
		if (values == null || values.size() <= 0)
			return null;
		return values.get(0);
	}

	@Override
	public List<String> getValues(String path) throws ConfigException {
		if (null == xml) {
			throw new ConfigException("The XML Document hasn't been loaded");
		}
		if (path == null)
			throw new ConfigException("The path can not be null");

		if (!path.startsWith("/"))
			if (currentPath != null && currentPath.length() != 0) {
				path = currentPath + '/' + path;
			} else {
				throw new ConfigException("incorrect path: " + path);
			}
		List<String> values = new LinkedList<String>();
		PathResolver resolver = new PathResolver();
		resolver.resolve(path);
		Token token = null;
		List<Element> elements = new LinkedList<Element>();
		elements.add(xml.getRootElement());
		String filtName = null;
		resolver.next();
		token = resolver.getToken();
		if (!token.token.equals(xml.getRootElement().getName()) || token.type != Token.TYPE.PATH_NAME)
			return values;
		while (resolver.next()) {
			token = resolver.getToken();
			switch (token.type) {
			case Token.TYPE.PATH_NAME:
				elements = searchTree(elements, token.token);
				if (elements.size() == 0)
					return values;
			case Token.TYPE.EXPRESS_LEFT:
				filtName = token.token;
				break;
			case Token.TYPE.EXPRESS_RIGHT:
				filterElement(elements, filtName, token.token);
				if (elements.size() == 0)
					return values;
				break;
			case Token.TYPE.VALUE_NAME:
				for (Element ele : elements) {
					String value = ele.attributeValue(token.token);
					if (value != null)
						values.add(value);
					@SuppressWarnings("unchecked")
					List<Element> eles = (List<Element>) ele
							.elements(token.token);
					for (Element e : eles) {
						value = e.getText().trim();
						if (value.length() > 0)
							values.add(value);
					}
				}
			}
		}
		return values;
	}

	@Override
	public void locate(String path) throws ConfigException {
		this.currentPath = path;
	}

	@SuppressWarnings("unchecked")
	private List<Element> searchTree(List<Element> eles, String token) {
		List<Element> elements = new LinkedList<Element>();
		for (Element ele : eles) {
			elements.addAll(ele.elements(token));
		}
		return elements;
	}

	private void filterElement(List<Element> eles, String name, String value) {
		value = trimString(value);
		for (int i = 0; i < eles.size(); i++) {
			Element ele = eles.get(i);
			if (!isElementMatch(ele, name, value))
				eles.remove(i);
		}
	}

	@SuppressWarnings("unchecked")
	private boolean isElementMatch(Element ele, String name, String value) {
		if (ele.attribute(name) != null && value.equals(ele.attribute(name).getValue()))
			return true;
		else {
			for (Element e : (List<Element>) ele.elements(name)) {
				if (e.getText().trim().equals(value))
					return true;
			}
		}
		return false;
	}
		
	private String trimString(String str) {
		str = str.trim();
		StringBuffer buffer = new StringBuffer(str);
		if (str.startsWith("'") && str.endsWith("'") || str.startsWith("\"")
				&& str.endsWith("\""))
			buffer.deleteCharAt(0).deleteCharAt(buffer.length() - 1);
		return buffer.toString();
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<String> getSubNames(String path) throws ConfigException {
		if (null == xml) {
			throw new ConfigException("The XML Document hasn't been loaded");
		}
		if (path == null)
			throw new ConfigException("The path can not be null");

		if (!path.startsWith("/")) {
			if (currentPath != null && currentPath.length() != 0) {
				path = currentPath + '/' + path;
			} else {
				throw new ConfigException("incorrect path: " + path);
			}
		}
		List<String> values = new LinkedList<String>();
		PathResolver resolver = new PathResolver();
		resolver.resolve(path);
		Token token = null;
		List<Element> elements = new LinkedList<Element>();
		elements.add(xml.getRootElement());
		String filtName = null;
		resolver.next();
		token = resolver.getToken();
		if (!token.token.equals(xml.getRootElement().getName()) || token.type != Token.TYPE.PATH_NAME)
			return null;
		while (resolver.next()) {
			token = resolver.getToken();
			switch (token.type) {
			case Token.TYPE.PATH_NAME:
				elements = searchTree(elements, token.token);
				if (elements.size() == 0)
					return null;
			case Token.TYPE.EXPRESS_LEFT:
				filtName = token.token;
				break;
			case Token.TYPE.EXPRESS_RIGHT:
				filterElement(elements, filtName, token.token);
				if (elements.size() == 0)
					return null;
				break;
			case Token.TYPE.VALUE_NAME:
				for (Element ele : elements) {
					List<Element> currEles = (List<Element>) ele.elements();
					for (Element curr : currEles) {
						List<Element> eles = (List<Element>) curr.elements();
						for (Element e : eles) {
							values.add(e.getName());
						}
						List<Attribute> attrs = curr.attributes();
						for (Attribute attr : attrs) {
							values.add(attr.getName());
						}
					}
				}
			}
		}
		return values;
	}
}
